package com.zh.common.utils;

import cn.hutool.core.collection.CollUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Redis工具类
 * 有些方法他们可能现在没有用，但在曾经或者将来会有用
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class RedisUtil {

    private final RedisTemplate<String, ?> redisTemplate;
    private final HashOperations<String, String, Object> hashOps;
    private final SetOperations<String, String> setOps;
    private final ZSetOperations<String, String> zSetOps;
    private final ObjectMapper objectMapper;

    /**
     * 设置键值对 默认31天
     *
     * @param key   键
     * @param value 值
     * @return 操作结果
     */
    public boolean set(String key, Object value) {
        if (key == null || key.trim().isEmpty()) {
            throw new IllegalArgumentException("Key cannot be null or empty");
        }
        return this.set(key, value, 31, TimeUnit.DAYS);
    }

    /**
     * 设置键值对并指定过期时间
     *
     * @param key      键
     * @param value    值
     * @param timeout  过期时间
     * @param timeUnit 时间单位
     * @return 操作结果
     */
    public <T> boolean set(String key, T value, long timeout, TimeUnit timeUnit) {
        if (key == null || key.trim().isEmpty()) {
            throw new IllegalArgumentException("Key cannot be null or empty");
        }
        try {
            ValueOperations<String, T> stringValueOperations = (ValueOperations<String, T>) redisTemplate.opsForValue();
            stringValueOperations.set(key, value, timeout, timeUnit);
            return Boolean.TRUE;
        } catch (Exception e) {
            log.error("[RedisUtil.set] [error] Failed to set key-value pair with timeout: {}", e.getMessage());
            return Boolean.FALSE;
        }
    }


    /**
     * 获取键对应的值
     *
     * @param key 键
     * @return 值
     */
    public <T> T get(String key) {
        if (key == null || key.trim().isEmpty()) {
            return null;
        }
        try {
            ValueOperations<String, T> stringValueOperations = (ValueOperations<String, T>) redisTemplate.opsForValue();
            return stringValueOperations.get(key);
        } catch (Exception e) {
            log.error("[RedisUtil.get] [error] Failed to get value for key: {}", key, e);
            return null;
        }
    }

    public Integer increment(String key, Integer n) {
        return Math.toIntExact(redisTemplate.opsForValue().increment(key, n));
    }

    /**
     * set中增加元素，支持一次增加多个元素，逗号分隔即可，结果返回添加的个数
     *
     * @param key
     * @param values
     */
    public void addSet(String key, String... values) {
        try {
            setOps.add(key, values);
        } catch (Exception e) {
            log.error("[RedisUtil.addSet] [error]", e);
        }
    }

    public void removeSet(String key, Object... values) {
        try {
            setOps.remove(key, values);
        } catch (Exception e) {
            log.error("[RedisUtil.removeSet] [error]", e);
        }
    }

    /**
     * 获取集合
     *
     * @param key
     * @return
     */
    public Set<String> getSet(String key) {
        try {
            return setOps.members(key);
        } catch (Exception e) {
            log.error("[RedisUtil.getSet] [error]", e);
            return null;
        }
    }

    /**
     * 集合长度
     *
     * @return
     */
    public Long getSetSize(String key) {
        try {
            return setOps.size(key);
        } catch (Exception e) {
            log.error("[RedisUtil.getSetSize] [error]", e);
            return null;
        }
    }

    /**
     * 判断是否在集合内
     *
     * @param key
     * @param value
     * @return
     */
    public Boolean isInSet(String key, String value) {
        try {
            return setOps.isMember(key, value);
        } catch (Exception e) {
            log.error("[RedisUtil.isInSet] [error]", e);
            return null;
        }
    }

    /**
     * 添加zset集合元素
     *
     * @param key
     * @param member
     * @param score
     */
    public void addZSet(String key, String member, Double score) {
        try {
            zSetOps.add(key, member, score);
        } catch (Exception e) {
            log.error("[RedisUtil.addZSet] [error]", e);
        }
    }

    /**
     * 删除zset集合元素
     *
     * @param key
     * @param values
     */
    public void removeZSet(String key, String... values) {
        try {
            zSetOps.remove(key, (Object) values);
        } catch (Exception e) {
            log.error("[RedisUtil.removeZSet] [error]", e);
        }
    }

    /**
     * 获取zset集合
     *
     * @param key
     * @return
     */
    public Set<String> getZSet(String key) {
        try {
            return zSetOps.range(key, 0, -1);
        } catch (Exception e) {
            log.error("[RedisUtil.getZSet] [error]", e);
            return null;
        }
    }

    /**
     * 获取范围内的元素
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<String> rangeZSet(String key, long start, long end) {
        try {
            return zSetOps.range(key, start, end);
        } catch (Exception e) {
            log.error("[RedisUtil.rangeZSet] [error]", e);
            return null;
        }
    }

    /**
     * 元素是否在集合中
     *
     * @param key
     * @param value
     * @return
     */
    public Boolean isInZSet(String key, String value) {
        Double score = zSetOps.score(key, value);
        return score != null;
    }

    /**
     * 获取zSet集合大小
     *
     * @param key
     * @return
     */
    public Long getZSetSize(String key) {
        try {
            return zSetOps.zCard(key);
        } catch (Exception e) {
            log.error("[RedisUtil.getZSetSize] [error]", e);
            return null;
        }
    }

    /**
     * 根据键列表批量获取对象列表
     *
     * @param keys      键列表
     * @param ascending 是否升序排序
     */

    public <T> List<T> getObjList(List<String> keys, boolean ascending) {
        if (keys == null || keys.isEmpty()) {
            return Collections.emptyList();
        }
        try {
            ValueOperations<String, T> valueOperations = (ValueOperations<String, T>) redisTemplate.opsForValue();
            List<T> values = valueOperations.multiGet(keys);

            if (values == null) {
                return Collections.emptyList();
            }

            if (ascending) {
                values.sort((o1, o2) -> {
                    if (o1 == null && o2 == null) return 0;
                    if (o1 == null) return -1;
                    if (o2 == null) return 1;
                    return ((Comparable<T>) o1).compareTo(o2);
                });
            } else {
                values.sort((o1, o2) -> {
                    if (o1 == null && o2 == null) return 0;
                    if (o1 == null) return 1;
                    if (o2 == null) return -1;
                    return ((Comparable<T>) o2).compareTo(o1);
                });
            }

            return values;
        } catch (Exception e) {
            log.error("Failed to get objects for keys: {}", keys, e);
            return Collections.emptyList();
        }
    }

    /**
     * 删除键
     *
     * @param key 键
     */
    public void delete(String key) {
        if (key == null || key.trim().isEmpty()) {
            return;
        }
        try {
            redisTemplate.delete(key);
        } catch (Exception e) {
            log.error("Failed to delete key: {}", e.getMessage(), e);
        }
    }

    /**
     * 批量删除键
     *
     * @param keys
     */
    public void deleteKeys(List<String> keys) {
        redisTemplate.delete(keys);
    }

    /**
     * 根据前缀批量删除缓存键
     *
     * @param prefix 缓存键的前缀
     */
    public void deleteByPrefix(String prefix) {
        Set<String> keysToDelete = new HashSet<>();
        try (Cursor<byte[]> cursor = Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection().scan(ScanOptions.scanOptions().match(prefix + "*").count(1000).build())) {
            while (cursor.hasNext()) {
                byte[] keyBytes = cursor.next();
                String key = new String(keyBytes, StandardCharsets.UTF_8);
                keysToDelete.add(key);
            }
        } catch (Exception e) {
            log.error("Error occurred while deleting keys with prefix {}: {}", prefix, e.getMessage());
        }

        if (!keysToDelete.isEmpty()) {
            redisTemplate.delete(keysToDelete);
            log.info("删除前缀{}共{}个键", prefix, keysToDelete.size());
        }
    }


    /**
     * 获取指定前缀的 key 集合，并按升序或降序排序
     *
     * @param prefix    前缀
     * @param ascending 是否升序排序
     * @return 排序后的 key 集合
     */
    public List<String> getKeysByPrefix(String prefix, boolean ascending) {
        if (prefix == null || prefix.trim().isEmpty()) {
            return Collections.emptyList();
        }
        List<String> keys = new ArrayList<>();

        try (Cursor<byte[]> cursor = Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection().scan(ScanOptions.scanOptions().match(prefix + "*").count(1000).build())) {
            while (cursor.hasNext()) {
                byte[] keyBytes = cursor.next();
                String key = new String(keyBytes, StandardCharsets.UTF_8);
                keys.add(key);
            }
        } catch (Exception e) {
            log.error("Error occurred while getting keys with prefix {}: {}", prefix, e.getMessage());
        }
        // 根据升序或降序条件排序
        if (ascending) {
            Collections.sort(keys);
        } else {
            Collections.sort(keys, Collections.reverseOrder());
        }
        return keys;
    }

    /**
     * 获取指定前缀key的值列表,并按升序或降序排序
     *
     * @param prefix    前缀
     * @param ascending 是否升序排序
     * @param <T>       值的类型
     * @return 排序后的值列表
     */
    public <T> List<T> getObjListByKeyPrefix(String prefix, boolean ascending) {
        if (prefix == null || prefix.trim().isEmpty()) {
            return Collections.emptyList();
        }

        List<String> keys = getKeysByPrefix(prefix, ascending);
        if (keys.isEmpty()) {
            return Collections.emptyList();
        }

        try {
            ValueOperations<String, T> valueOperations = (ValueOperations<String, T>) redisTemplate.opsForValue();
            return valueOperations.multiGet(keys);
        } catch (Exception e) {
            log.error("Failed to get values for keys with prefix {}: {}", prefix, e.getMessage());
            return Collections.emptyList();
        }
    }

    /**
     * 检查键是否存在
     *
     * @param key 键
     * @return 是否存在
     */
    public boolean exists(String key) {
        if (key == null || key.trim().isEmpty()) {
            return Boolean.FALSE;
        }
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }

    public long getExpire(String key) {
        if (key == null || key.trim().isEmpty()) {
            return -1;
        }
        // 调用 RedisTemplate 的方法获取 key 的过期时间（以秒为单位）
        Long expire = redisTemplate.getExpire(key, TimeUnit.SECONDS);

        // 使用 Optional 处理可能的 null 值
        return Optional.ofNullable(expire)
                .orElse(-1L);
    }

    /**
     * 将用户的多个属性存储在一个哈希中
     *
     * @param key    哈希的键
     * @param fields 属性Map
     */
    public void setHash(String key, Map<String, ?> fields, long timeout, TimeUnit timeUnit) {
        hashOps.putAll(key, fields);
        redisTemplate.expire(key, timeout, timeUnit);
    }

    /**
     * 获取key的属性
     *
     * @param key 用户ID
     * @return 用户属性Map
     */
    public <V> Map<String, V> getHashEntries(String key) {
        @SuppressWarnings("unchecked")
        Map<String, V> entries = (Map<String, V>) hashOps.entries(key);
        return entries;
    }

    public <T> void setList(String key, List<T> list, long timeout, TimeUnit timeUnit) {
        if (key == null || key.trim().isEmpty()) {
            logKeyNull();
        }
        if (CollUtil.isEmpty(list)) {
            log.error("list is empty");
            return;
        }
        try {
            // 使用JSON序列化
            List<String> jsonStringList = list.stream()
                    .map(item -> {
                        try {
                            return objectMapper.writeValueAsString(item);
                        } catch (JsonProcessingException e) {
                            log.error("Failed to serialize object to JSON: {}", e.getMessage(), e);
                            return null;
                        }
                    })
                    .collect(Collectors.toList());
            ListOperations<String, String> listOps = (ListOperations<String, String>) redisTemplate.opsForList();
            assert key != null;
            listOps.rightPushAll(key, jsonStringList);
            redisTemplate.expire(key, timeout, timeUnit);
        } catch (Exception e) {
            log.error("Error occurred while setting list", e);
        }
    }

    public <T> void setList(String key, List<T> list) {
        this.setList(key, list, 7, TimeUnit.DAYS);
    }

    public <T> List<T> getList(String key, Class<T> clazz) {
        if (key == null || key.trim().isEmpty()) {
            return null;
        }
        try {
            // 从 Redis 中获取字符串列表
            List<String> jsonStringList = (List<String>) redisTemplate.opsForList().range(key, 0, -1);
            if (jsonStringList == null || jsonStringList.isEmpty()) {
                return null;
            }

            // 使用 Jackson 反序列化每个字符串
            List<T> resultList = jsonStringList.stream()
                    .map(jsonString -> {
                        try {
                            return objectMapper.readValue(jsonString, clazz);
                        } catch (Exception e) {
                            log.error("Error deserializing object: {}", jsonString, e);
                            return null;
                        }
                    })
                    .filter(Objects::nonNull) // 过滤掉反序列化失败的 null 对象
                    .collect(Collectors.toList());

            return resultList;
        } catch (Exception e) {
            log.error("Error occurred while getting list", e);
            return null;
        }
    }

    public void logKeyNull() {
        log.error("Key cannot be null or empty");
    }


}
